<?php

/**
 * This file is part of the Codizer package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright   Copyright (c) 2010 Advisa (http://www.advisa.fr)
 * @author      Codizer Core Team <team@codizer.org>
 * @package     Codizer
 */

class f_relation_ManagerNew
{
    private $document;
    /* @var $query f_relation_RelationQueryBuilder */
    private $query;
    private $recursive = false;
    private $levelLimit;

    private $queryType;

	/**
	 * @return f_relation_ManagerNew
	 */
	private function __construct($document)
	{
        $this->document = $document;
        $this->query = f_relation_RelationQueryBuilder::getInstance();
		return $this;
	}

	/**
	 * Get instance
	 *
	 * @return f_relation_ManagerNew
	 */
	public static function getInstance($document)
	{
        return new self($document);
	}

// ############################################################################## INSERT / UPDATE METHODS

	/**
	 * Add/update a relation in f_relation
	 *
	 * @param f_document_BaseDocument $parentDocument
	 * @param f_document_BaseDocument $childDocument
	 * @param string $relationType
	 * @param integer $orderNumber
	 * @param string $lang
	 */
	public function addRelation($childDocument, $relationType)
	{
		$dbProvider = f_database_Provider::getInstance();

        if($this->document->getLang() != $childDocument->getLang())
        {
            trigger_error('Relation : lang is not the same between documents', E_USER_ERROR);
        }

        $lang = $this->document->getLang();
		
		$lastNumber = $this->getLastOrderNumber();
		$orderNumber = $lastNumber + 1;
		
		$params = array();				
		
		$params['parent_id'] = $this->document->getUniqueId();
		$params['child_id'] = $childDocument->getUniqueId();
		$params['relation_type'] = $relationType;
		$params['parent_model'] = $this->document->getModel();
		$params['child_model'] = $childDocument->getModel();
		$params['relation_order'] = $orderNumber;
		$params['lang'] = $lang;
		
		// If the relation does not exist in f_relation...
		if(!$this->checkIfRelationExists($childDocument, $relationType))
		{
			// and documents are not DELETED...
			if($this->document->getPublicationstatus() != "DELETED" && $childDocument->getPublicationstatus() != "DELETED")
			{			
				$dbProvider->insert("f_relation", $params);
			}		
		}
		// else if relation exists...
		else
		{			
			// and one document is deleted...
			if($this->document->getPublicationstatus() == "DELETED" || $childDocument->getPublicationstatus() == "DELETED")
			{					
				// clear relation...
				$this->clearRelation($childDocument, $relationType, $lang);
			}
			else
			{
				// Update
				$conditions['parent_id'] = $params['parent_id'];
				$conditions['child_id'] = $params['child_id'];
				$conditions['relation_type'] = $params['relation_type'];
				$conditions['lang'] = $params['lang'];				
				
				$dbProvider->updateByKey("f_relation", $conditions ,$params);
			}
		}
	}
	
	/**
	 * Retrieve the last order number
	 *
	 * @param f_document_BaseDocument $parentDocument
	 * @param string $relationType
	 * @return integer
	 */
	public function getLastOrderNumber()
	{
		$dbProvider = f_database_Provider::getInstance();
		
		$sql = 'SELECT `relation_order` FROM `f_relation` WHERE `parent_id`=:parentId AND `parent_model`=:parentModel ORDER BY `relation_order` DESC LIMIT 1';
		
		return $dbProvider->setQuery($sql)->execute(array("parentId" => $this->document->getUniqueId(), "parentModel" => $this->document->getModel()))->getQueryResult(PDO::FETCH_COLUMN);
	}

    public function getNextDocument($childDocument, $relationType)
    {
        $dbProvider = f_database_Provider::getInstance();

        $sql = 'SELECT `child_id`, `child_model` FROM `f_relation` WHERE `parent_id`=:parentId AND `parent_model`=:parentModel AND `relation_type`=:relationType AND `lang`=:lang AND `relation_order`> (SELECT `relation_order` FROM `f_relation` WHERE `child_id`=:childId AND `child_model`=:childModel LIMIT 1) ORDER BY relation_order ASC LIMIT 1';

        $result = $dbProvider->setQuery($sql)->execute(array(
                'parentId' => $this->document->getUniqueId(),
                'parentModel' => $this->document->getModel(),
                'relationType' => $relationType,
                'lang' => $this->document->getLang(),
                'childId' => $childDocument->getUniqueId(),
                'childModel' => $childDocument->getModel()))
                ->getQueryResult(PDO::FETCH_ASSOC);
        
        if(count($result) > 0)
        {
            return f_document_Provider::getInstance()->getByModelAndUniqueId($result['child_model'], $result['child_id'], $this->document->getLang());
        }

        return null;
    }

    public function getPreviousDocument($childDocument, $relationType)
    {
        $dbProvider = f_database_Provider::getInstance();

        $sql = 'SELECT `child_id`, `child_model` FROM `f_relation` WHERE `parent_id`=:parentId AND `parent_model`=:parentModel AND `relation_type`=:relationType AND `lang`=:lang AND `relation_order`< (SELECT `relation_order` FROM `f_relation` WHERE `child_id`=:childId AND `child_model`=:childModel LIMIT 1) ORDER BY relation_order DESC LIMIT 1';

        $result = $dbProvider->setQuery($sql)->execute(array(
                'parentId' => $this->document->getUniqueId(),
                'parentModel' => $this->document->getModel(),
                'relationType' => $relationType,
                'lang' => $this->document->getLang(),
                'childId' => $childDocument->getUniqueId(),
                'childModel' => $childDocument->getModel()))
                ->getQueryResult(PDO::FETCH_ASSOC);

        if(count($result) > 0)
        {
            return f_document_Provider::getInstance()->getByModelAndUniqueId($result['child_model'], $result['child_id'], $this->document->getLang());
        }

        return null;
    }

    /**
     * Retriever the order number of the relation
     *
     * @param document $parentDocument
     * @param document $childDocument
     * @param string $relationType
     * @param string $lang
     * @return string
     */
    public function getOrderNumber($childDocument, $relationType, $lang)
    {
        $dbProvider = f_database_Provider::getInstance();

		$sql = 'SELECT relation_order FROM f_relation WHERE parent_id=:parentid AND `parent_model`=:parentModel AND child_id=:childid AND `child_model`=:childModel AND relation_type=:relationType AND lang=:lang';

		$results = array();

		$results = $dbProvider->setQuery($sql)
							  ->execute(array('parentid'		=>	$this->document->getUniqueId(),
                                              'parentModel'     =>  $this->document->getModel(),
											  'childid'			=>	$childDocument->getUniqueId(),
											  'childModel'		=>	$childDocument->getModel(),
											  'relationType'	=>	$relationType,
							  				  'lang'			=>	$lang))
							  ->getQueryResults(PDO::FETCH_COLUMN);

		return $results[0];
    }
		
	/**
	 * Check if a relation already exist
	 *
	 * @param f_document_BaseDocument $parentDocument
	 * @param f_document_BaseDocument $childDocument
	 * @param string $relationType
	 * @param integer $orderNumber
	 * @return boolean
	 */
	public function checkIfRelationExists($childDocument, $relationType, $lang = null)
	{
		$dbProvider = f_database_Provider::getInstance();
		
		$sql = 'SELECT relation_type FROM f_relation WHERE parent_id=:parentid AND `parent_model`=:parentModel AND child_id=:childid AND `child_model`=:childModel AND relation_type=:relationType AND lang=:lang';
		
		$results = array();
		
		$results = $dbProvider->setQuery($sql)
							  ->execute(array('parentid'		=>	$this->document->getUniqueId(),
                                              'parentModel'     =>  $this->document->getModel(),
											  'childid'			=>	$childDocument->getUniqueId(),
											  'childModel'		=>	$childDocument->getModel(),
											  'relationType'	=>	$relationType,
							  				  'lang'			=>	$lang))
							  ->getQueryResults(PDO::FETCH_COLUMN);
		
		return isset($results[0]); 
	}
	
	/**
	 * Delete a relation
	 *
	 * @param f_document_BaseDocument $parentDocument
	 * @param f_document_BaseDocument $childDocument
	 * @param string $relationType
	 * @param string $lang
	 */
	public function clearRelation($childDocument, $relationType)
	{
		$dbProvider = f_database_Provider::getInstance();	
		$cache = f_core_Cache::getInstance();

        if($this->document->getLang() != $childDocument->getLang())
        {
            trigger_error('Relation : lang is not the same between documents', E_USER_ERROR);
        }

        $lang = $this->document->getLang();

        $rel = new self($childDocument);

        $parents = $rel->getParents()->withLang($lang)->withRelationType($relationType)->retrieveIdsAndModels();
		
		$sql = 'DELETE FROM f_relation WHERE parent_id=:parentid AND `parent_model`=:parentModel AND child_id=:childid AND `child_model`=:childModel AND relation_type=:relationType AND lang=:lang';
		
		$results = array();
		
		$results = $dbProvider->setQuery($sql)
							  ->execute(array('parentid'		=>	$this->document->getUniqueId(),
                                              'parentModel'     =>  $this->document->getModel(),
											  'childid'			=>	$childDocument->getUniqueId(),
											  'childModel'		=>	$childDocument->getModel(),
											  'relationType'	=>	$relationType,
							  				  'lang'			=>	$lang));

		if(count($parents) >= 1)
		{
			foreach($parents as $parent)
			{
				$cache->clearCache($cache->getDocumentCacheKey($parent['model'], $parent['id'], $lang));
			}
		}
	}
	
	/**
	 * Remove all relations of all kind for a given document
	 * 
	 * @param $document
	 * @param $lang
	 */
	public function clearAllRelations($document, $lang)
	{
		$dbProvider = f_database_Provider::getInstance();	
		$cache = f_core_Cache::getInstance();

		if(is_null($lang))
		{
			$context = f_core_Context::getInstance();
			$lang = $context->getLang();
		}
		
		$sql = "SELECT `parent_id` FROM `f_relation` WHERE `child_id`=:childId AND `lang`=:lang";
		
		$parentsIds = $dbProvider->setQuery($sql)->execute(array("childId" => $document->getUniqueId(), "lang" => $lang))->getQueryResults(PDO::FETCH_COLUMN);
		
		$sql = "DELETE FROM f_relation WHERE parent_id=:documentId OR child_id=:documentId AND lang=:lang";
		
		$results = array();
		
		$results = $dbProvider->setQuery($sql)
							  ->execute(array("documentId"		=>	$document->getUniqueId(),
							  				  "lang"			=>	$lang));

		if(count($parentsIds) >= 1)
		{
			foreach($parentsIds as $parentId)
			{
				$cache->clearCache($cache->getDocumentCacheKey($parentId, $lang));	
			}
		}		
	}
	
	/**
	 * Remove parents cache
	 * 
	 * @param integer $childId
	 * @param string $relationType
	 * @param integer $lang
	 * @return nb of affected lines
	 */
	public function regenerateParentCache($childId, $relationType, $lang = null)
	{
		if(is_null($lang))
		{
			$lang = f_core_Context::getInstance()->getLang();
		}
				
		$parents = $this->getAllParents($childId, $relationType, $lang);
		
		$count = 0;
		
		if(count($parents) >= 1)
		{
			$cache = f_core_Cache::getInstance();
			
			foreach ($parents as $parent)
			{
				$cache->clearCache($cache->getDocumentCacheKey($parent->getUniqueId(), $lang));
				$count++;
			}
		}
		
		return $count;
	}
	
	/**
	 * Order or re-order relations for a given document
	 * 
	 * @example orderRelations(25, "FR", array(0 => 23, 1 => 28, 2 => 33))
	 * @param integer $parentId
	 * @param string $relationLang
	 * @param array $orderingArray
	 * @return f_relation_Manager
	 */
	public function orderRelations($parentId, $relationLang, $orderingArray)
	{
		$dbProvider = f_database_Provider::getInstance();
		
		$dbProvider->beginTransaction();
		foreach($orderingArray as $orderNb => $childId)
		{
			$dbProvider->updateByKey("f_relation", array("parent_id" => $parentId, "child_id" => $childId, "lang" => strtoupper($relationLang)), array("relation_order" => $orderNb));			
		}
		$dbProvider->commit();
		
		return $this;
	}

// ############################################################################## REQUEST QUERY

    /**
     * Initialize request to get parents documents
     * @return f_relation_Manager
     */
    public function getParents()
    {
        $this->queryType = 'parents';
        $this->query->setChild($this->document)
                    ->addColumnToGet('parent_id')
                    ->addColumnToGet('parent_model')
                    ->addColumnToGet('lang')
                    ->addColumnToGet('relation_order');

        return $this;
    }

    /**
     * Initialize request to get childs documents
     * @return f_relation_Manager
     */
    public function getChilds()
    {
        $this->queryType = 'child';
        $this->query->setParent($this->document)
                    ->addColumnToGet('child_id')
                    ->addColumnToGet('child_model')
                    ->addColumnToGet('lang')
                    ->addColumnToGet('relation_order');

        return $this;
    }

    /**
     * Get relation with order ASC or DESC
     *
     * @param string ASC or DESC
     * @return f_relation_Manager
     */
    public function orderBy($order)
    {
        $order = "add".ucfirst($order)."Order";
        $this->query->$order('relation_order');
        return $this;
    }

    /**
     * Get relation with relation type
     *
     * @param string $type
     * @return f_relation_Manager
     */
    public function withRelationType($type)
    {
        $this->query->addEqualCondition('relation_type', $type, 'OR');
        return $this;
    }

    /**
     * Get relation with model
     *
     * @param string|array $model
     * @return f_relation_Manager
     */
    public function withModel($models)
    {
        if(!is_array($models))
        {
            $models = array($models);
        }

        foreach($models as $model)
        {
            $model = str_replace('modules_', '', $model);
            $model = 'modules_'.$model;
            
            if($this->queryType == 'child')
            {
                $this->query->addEqualCondition('child_model', $model, 'OR');
            }
            else
            {
                $this->query->addEqualCondition('parent_model', $model, 'OR');
            }
        }
        
        return $this;
    }

    /**
     * Must we get relations recursively
     *
     * @return f_relation_Manager
     */
    public function recursively()
    {
        $this->recursive = true;
        return $this;
    }

    /**
     * Must we get relations recursively and set a recursive limit
     *
     * @return f_relation_Manager
     */
    public function withRecursiveLimit($levelLimit = null)
    {
        $this->levelLimit = $levelLimit;
        return $this;
    }

    /**
     * Retrieve relations in a single lang
     *
     * @param string $lang
     * @return f_relation_Manager
     */
    public function withLang($lang)
    {
        if($lang != "*")
        {
            $this->query->addEqualCondition('lang', strtoupper($lang));
        }
        
        return $this;
    }

    /**
     * Limit result list
     *
     * @param integer $nbResult
     * @param integer $offset
     * @return f_relation_Manager
     */
    public function limit($nbResult, $offset = 0)
    {
        $this->query->addLimit($nbResult, $offset);
        return $this;
    }

    /**
     * Retrieve array of documents
     *
     * @return array
     */
    public function retrieveDocuments($onlyPublished = false)
    {
        $idsAndModelds = $this->retrieveIdsAndModels();

        $result = array();

        foreach($idsAndModelds as $idsAndModelds)
        {
            $doc = f_document_Provider::getInstance()->getByModelAndUniqueId($idsAndModelds['model'], $idsAndModelds['id'], $idsAndModelds['lang']);
            if($onlyPublished == false || $doc->getPublicationStatus() == 'PUBLISHED')
            {
                $result[] = $doc;
            }
        }

        return $result;
    }

    /**
     * Retrieve array of ids, models, lang and model
     *
     * @return array
     */
    public function retrieveIdsAndModels()
    {        
        if(!$this->recursive)
        {
            $dbProvider = f_database_Provider::getInstance();

            $dbNodes = $dbProvider->setQuery($this->query->getQuery())->execute($this->query->getValues())->getQueryResults(PDO::FETCH_ASSOC);

            $nodes = array();

            if(count($dbNodes) > 0)
            {
                foreach($dbNodes as $node)
                {
                    $nodes[] = $this->getNode($node);
                }                
            }

            return $nodes;
        }
        else
        {
            return $this->retrieveIdsAndModelsRecursive($this->document->getUniqueId(), $this->document->getModel(), $this->query->getQuery());
        }
    }

    /**
     * Retrieve recursively
     *
     * @param integer $nodeId
     * @param string $nodeModel
     * @param string $query
     * @param integer $level
     * @param array $result
     * @return array
     */
    private function retrieveIdsAndModelsRecursive($nodeId, $nodeModel, $query, $level = 0, $result = array())
    {
        $dbProvider = f_database_Provider::getInstance();

        $values = $this->query->getValues();
        if($this->queryType == 'child')
        {
            $level++;
            $values['eqparent_id0'] = $nodeId;
            $values['eqparent_model1'] = $nodeModel;
        }
        else
        {
            $level--;
            $values['eqchild_id0'] = $nodeId;
            $values['eqchild_model1'] = $nodeModel;
        }


        $nodes = $dbProvider->setQuery($query)->execute($values)->getQueryResults(PDO::FETCH_ASSOC);

        if(!is_null($this->levelLimit))
        {
            if(abs($level) > $this->levelLimit)
            {
                return $result;
            }
        }

        if(is_array($nodes) && count($nodes) > 0)
        {
             foreach($nodes as $node)
             {
                 $node['level'] = $level;
                 
                 $node = $this->getNode($node);
                 
                 $result[] = $node;
                 $result = $this->retrieveIdsAndModelsRecursive($node['id'], $node['model'], $query, $level, $result);
             }
        }

        return $result;
    }

    private function getNode($node)
    {
        if(isset($node['parent_id']))
        {
            $node['id'] = $node['parent_id'];
            $node['model'] = $node['parent_model'];
            $node['type'] = 'parent';
            unset($node['parent_id']);
            unset($node['parent_model']);
        }
        else
        {
             $node['id'] = $node['child_id'];
             $node['model'] = $node['child_model'];
             $node['type'] = 'child';
             unset($node['child_id']);
             unset($node['child_model']);
        }
        return $node;
    }

    /**
     * Count results
     * 
     * @return integer
     */
    public function count()
    {
        return count($this->retrieveIdsAndModels());
    }
}